package com.gzsxy.esjy.redis.extend.impl;

import com.gzsxy.esjy.common.base.exception.impl.CustomAssert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * @author xiaolong
 * @version 1.0
 * @description: 同步锁获取（内部实现）
 * @date 2022/6/9 13:58
 */
@ConditionalOnProperty(name = "gosuncn.lock.local",havingValue = "memory",matchIfMissing = true)
public class DefaultDistributeLock extends BaseDistributeLock{

    private static final String DEFAULT_DEVICE = "default";

    private static final Logger log = LoggerFactory.getLogger(DefaultDistributeLock.class);

    private final Map<String,Boolean> map = new ConcurrentHashMap<String,Boolean>();

    private final Map<String,Map<String,String>> asyncMap = new HashMap<String,Map<String,String>>();

    @Value("${gosun.synchronized.hash.device:24}")
    private int devices;

    public DefaultDistributeLock(int devices){
        this.devices = devices;
        init();
    }

    public DefaultDistributeLock(){
    }

    @PostConstruct
    private void init(){
        initDevice(DefaultDistributeLock.DEFAULT_DEVICE);
    }

    private Map<String,String> initDevice(String device){
        synchronized (asyncMap){
            Map<String, String> defaultAsyncMap = new HashMap<>();
            for (int i = 0; i < devices; i++) {
                defaultAsyncMap.put(""+i,"");
            }
            asyncMap.put(device,defaultAsyncMap);
            return defaultAsyncMap;
        }
    }

    @Override
    public boolean tryLock(String key, String device, long maxWaitTime, long timeInterval) {
        try {
            int count = (int) (maxWaitTime / timeInterval);
            int i = 0;
            while (i<count){
                if (getLock(key,device)){
                    return true;
                }
                try {
                    Thread.sleep(timeInterval);
                } catch (InterruptedException e) {
                    log.error(e.getMessage(),e);
                }
                i++;
            }
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }
        return false;
    }

    /**
     * @description: 获得锁
     * @param
     * @return
     */
    private boolean getLock(String key,String device){
        //Math.abs数的绝对值
        synchronized (getkey(Math.abs(key.hashCode()% devices)+"",device)){
            if (!map.containsKey(key)){
                map.put(key,true);
                log.info("get key :{}",key);
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * @description: 获取键
     * @param
     * @return
     */
    private String getkey(String hascode,String device){
        Map<String, String> map2 = asyncMap.get(device);
        if (map2 == null){
            map2 = initDevice(device);
        }
        return map2.get(hascode);
    }


    @Override
    public void unLock(String key, String device) {
        if (null != key){
            map.remove(key);
            log.info("remove key : {}",key);
        }
    }

    @Override
    public boolean tryLock(String[] keys, String device, long maxWaitTime, long timeInterval) {
        boolean flag = true;
        int  i = 1;
        for (String key :keys){
            if (tryLock(key,device,maxWaitTime/(i++),timeInterval)){
                continue;
            }
            flag = false;
            break;
        }
        return flag;
    }

    @Override
    public void unLock(String[] keys, String device) {
        //从后往前
        for (int i = keys.length-1; i>=0; i--){
            unLock(keys[i],device);
        }
    }


    @Override
    public Object excute(Function<Lock[], Object> excute, Lock[] locks) {
        Object result = null;
        boolean flag = true;
        try {
            CustomAssert.notTrue(locks != null && locks.length >= 0, "锁不能为空");
            for (Lock lock :locks){
                if (tryLock(lock.getKey(),lock.getGroup(),lock.getMaxWaitTime(),lock.getTimeInterval())){
                    continue;
                }
                flag = false;
                CustomAssert.notTrue(true,lock.getFailMsg(),504);
                break;
            }
            result = excute.apply(locks);
        } finally {
            if (flag){
                for (int i = locks.length -1;i >= 0; i --){
                    unLock(locks[i].getKey(),locks[i].getGroup());
                }
            }
        }
        return result;
    }
}
